package com.altio.swing.portlet;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Transparency;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JComponent;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.ComponentUI;

import com.altio.swing.portlet.PortletPanelButton.ActionOperation;

public class PortletPanelUI extends ComponentUI {
  
  private int dropShadowWidth = 8;
  private int roundedCornerSize = 16;
  private int titleHeight = 30;
  private int buttonWidth = 16;
  private int buttonBorder = 3;
  
  private Map<Dimension, Image> shadowCache = new HashMap<Dimension, Image>();
  
  
  private static PortletPanelUI instance = null;

  /*
   * (non-Javadoc)
   * 
   * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
   */
  public static ComponentUI createUI(JComponent c) {
    
    if(instance == null) {
      instance = new PortletPanelUI();
    }
    
    return instance;
  }
  
  @Override
  public void installUI(final JComponent c) {
    
    if(!(c instanceof PortletPanel)) {
      return;
    }
    
    final PortletPanel panel = (PortletPanel) c;
    
    c.setBorder(new EmptyBorder(titleHeight + dropShadowWidth, dropShadowWidth, dropShadowWidth, dropShadowWidth));
    
    final PortletPanelButton closeButton = new PortletPanelButton(panel, ActionOperation.CLOSE);
    closeButton.setBackground(c.getBackground());
    c.add(closeButton);

    final PortletPanelButton minimizeButton = new PortletPanelButton(panel, ActionOperation.MINIMIZE);
    minimizeButton.setBackground(c.getBackground());
    c.add(minimizeButton);
    
    
    c.setLayout(new BorderLayout() {

      @Override
      public void layoutContainer(Container container) {
        super.layoutContainer(panel);
        
        closeButton.setVisible(panel.isClosable());
        
        int currentButtonInset = (dropShadowWidth + buttonWidth + roundedCornerSize);
        if(closeButton.isVisible()) {
          closeButton.setSize(buttonWidth, titleHeight);
          closeButton.setLocation(panel.getWidth() - currentButtonInset, dropShadowWidth);
          currentButtonInset += buttonWidth + buttonBorder;
        }
        
        if(minimizeButton.isVisible()) {
          minimizeButton.setSize(buttonWidth, titleHeight);
          minimizeButton.setLocation(panel.getWidth() - currentButtonInset, dropShadowWidth);
          currentButtonInset += buttonWidth + buttonBorder;
        }
      }
    });
    c.setOpaque(false);
  }
  
  @Override
  public void paint(Graphics g, JComponent c) {
    // No need to type check, if you've assigned this wrongly 
    // a class cast exception is appropriate
    PortletPanel toPaint = (PortletPanel)c;
    
    Graphics2D g2d = (Graphics2D)g.create();

    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);

    paintShadow(g2d, toPaint);

    fillTitleGradient(g2d, toPaint);

    paintTitleTopShadow(g2d, toPaint);

    paintTitleTopHighlight(g2d, toPaint);

    paintCaption(g2d, toPaint);


    g2d.dispose();
  }

  private void paintCaption(Graphics2D g2d, PortletPanel c) {
    FontMetrics fm = g2d.getFontMetrics(c.getFont());
    Rectangle totalBounds = new Rectangle(dropShadowWidth + (roundedCornerSize / 2), dropShadowWidth, c.getWidth() - (2 * (dropShadowWidth + (roundedCornerSize / 2))), titleHeight);
    Rectangle textBounds = new Rectangle();
    String possiblyClipped = SwingUtilities.layoutCompoundLabel(fm, c.getCaption(), null, SwingConstants.CENTER, SwingConstants.LEFT, SwingConstants.CENTER, SwingConstants.LEFT, totalBounds, new Rectangle(), textBounds, 0);

    g2d.translate(textBounds.x, textBounds.y + fm.getAscent());
    
    // Poor performance arguably aesthetic
//    FontRenderContext frc = g2d.getFontRenderContext();
//    TextLayout tl = new TextLayout(possiblyClipped, c.getFont(), frc);
//    Shape shape= tl.getOutline(null);
//    paintBorderShadow(g2d, 4, shape, getColorCopyWithAlpha(c.getBackground().darker(), 128));

    g2d.setColor(c.getForeground());
    g2d.drawString(possiblyClipped, 0, 0);
  }

  private void paintTitleTopHighlight(Graphics2D g2d, PortletPanel c) {
    Color brighterColor = c.getBackground().brighter();
    Color alpha = getColorCopyWithAlpha(brighterColor, 0);
    g2d.setPaint(new GradientPaint(dropShadowWidth, dropShadowWidth, brighterColor, dropShadowWidth, dropShadowWidth + (titleHeight / 2), alpha));
    g2d.drawRoundRect(dropShadowWidth, dropShadowWidth, (c.getWidth() - 1) - (2 * dropShadowWidth), titleHeight, roundedCornerSize, roundedCornerSize);

    g2d.setStroke(new BasicStroke(2));
    brighterColor = getColorCopyWithAlpha(brighterColor, 64);
    alpha = getColorCopyWithAlpha(brighterColor, 0);
    g2d.setPaint(new GradientPaint(dropShadowWidth, dropShadowWidth, brighterColor, dropShadowWidth, dropShadowWidth + (titleHeight / 2), alpha));
    g2d.drawRoundRect(dropShadowWidth, dropShadowWidth, (c.getWidth() - 1) - (2 * dropShadowWidth), titleHeight, roundedCornerSize, roundedCornerSize);
  }

  private void paintTitleTopShadow(Graphics2D g2d, PortletPanel c) {
    Color darkerColor = c.getBackground().darker();
    Color alpha = getColorCopyWithAlpha(darkerColor, 0);
    g2d.setPaint(new GradientPaint(dropShadowWidth, dropShadowWidth, darkerColor, dropShadowWidth, dropShadowWidth + (dropShadowWidth / 2), alpha));
    g2d.fillRoundRect(dropShadowWidth, dropShadowWidth, (c.getWidth()) - (2 * dropShadowWidth), dropShadowWidth * 2, roundedCornerSize, roundedCornerSize);

    darkerColor = getColorCopyWithAlpha(c.getBackground().darker(), 128);
    alpha = getColorCopyWithAlpha(darkerColor, 0);
    g2d.setPaint(new GradientPaint(dropShadowWidth, dropShadowWidth, darkerColor, dropShadowWidth, dropShadowWidth + dropShadowWidth, alpha));
    g2d.fillRoundRect(dropShadowWidth, dropShadowWidth, (c.getWidth()) - (2 * dropShadowWidth), dropShadowWidth * 2, roundedCornerSize, roundedCornerSize);
  }

  private void fillTitleGradient(Graphics2D g2d, PortletPanel c) {
    g2d.setPaint(new GradientPaint(dropShadowWidth, dropShadowWidth, c.getBackground(), dropShadowWidth, dropShadowWidth + titleHeight, c.getBackground().darker()));
    g2d.fillRoundRect(dropShadowWidth, dropShadowWidth, (c.getWidth()) - (2 * dropShadowWidth), titleHeight, roundedCornerSize, roundedCornerSize);
    g2d.fillRect(dropShadowWidth, dropShadowWidth + (titleHeight - roundedCornerSize), (c.getWidth()) - (2 * dropShadowWidth), roundedCornerSize);
  }

  private void paintShadow(Graphics2D g2d, PortletPanel c) {
    
    Image cachedShadow = shadowCache.get(c.getSize());
    
    if(cachedShadow == null) {

      GraphicsConfiguration gc = g2d.getDeviceConfiguration();
      BufferedImage img = gc.createCompatibleImage(c.getWidth(), c.getHeight(), Transparency.TRANSLUCENT);
      Graphics2D g2dImg = img.createGraphics();
      
      
      g2dImg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON);

      RoundRectangle2D topRectangle = new RoundRectangle2D.Float(dropShadowWidth, dropShadowWidth, (c.getWidth() - 1) - (2 * dropShadowWidth), titleHeight, roundedCornerSize, roundedCornerSize);
      
      int shadowHeight= Math.max(titleHeight - dropShadowWidth, (c.getHeight() - 1) - (3 * dropShadowWidth));
      RoundRectangle2D bottomRectangle = new RoundRectangle2D.Float(dropShadowWidth, dropShadowWidth * 2, (c.getWidth() - 1) - (2 * dropShadowWidth), shadowHeight, 5, 5);
      paintBorderShadow(g2dImg, dropShadowWidth, topRectangle, getColorCopyWithAlpha(Color.darkGray, 45));
      paintBorderShadow(g2dImg, dropShadowWidth, bottomRectangle, getColorCopyWithAlpha(Color.darkGray, 45));

      cachedShadow = img;
      shadowCache.put(c.getSize(), cachedShadow);
    }
    
    
    g2d.drawImage(cachedShadow, 0, 0, c);
  }

  private static void paintBorderShadow(Graphics2D g2, int shadowWidth, Shape toDraw, Color shadowColor) {
    final Color transparentShadow = getColorCopyWithAlpha(shadowColor, 0);
    int sw = shadowWidth*2;
    for (int i=sw; i >= 2; i-=2) {
      float pct = (float)(sw - i) / (sw - 1);
      g2.setColor(getMixedColor(shadowColor, pct,
          transparentShadow, 1.0f-pct));
      g2.setStroke(new BasicStroke(i));
      g2.draw(toDraw);
    }
  }

  private static Color getColorCopyWithAlpha(Color color, int alpha) {
    return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
  }

  private static Color getMixedColor(Color c1, float pct1, Color c2, float pct2) {
    float[] clr1 = c1.getComponents(null);
    float[] clr2 = c2.getComponents(null);
    for (int i = 0; i < clr1.length; i++) {
      clr1[i] = (clr1[i] * pct1) + (clr2[i] * pct2);
    }
    return new Color(clr1[0], clr1[1], clr1[2], clr1[3]);
  }
}
